
# used for helping decouple from webrick oddly enough
require 'thread'
require 'webrick/htmlutils'
require 'webrick/httputils'
require 'webrick/httpstatus'


#
# stolen from webrick ... so that i can return files in a standalone or proxied solution
#
# the server class has to provide .logger and .mimetypes
#

class HTTPServletError < StandardError; end

class AbstractServlet

      def self.get_instance(config, *options)
        self.new(config, *options)
      end

      def initialize(server, *options)
        @server = @config = server
        @options = options
      end

      def service(req, res)
        method_name = "do_" + req.request_method.gsub(/-/, "_")
        if respond_to?(method_name)
          __send__(method_name, req, res)
        else
          raise HTTPStatus::MethodNotAllowed,
                "unsupported method `#{req.request_method}'."
        end
      end

      def do_GET(req, res)
        raise HTTPStatus::NotFound, "not found."
      end

      def do_HEAD(req, res)
        do_GET(req, res)
      end

      def do_OPTIONS(req, res)
        m = self.methods.grep(/^do_[A-Z]+$/)
        m.collect!{|i| i.sub(/do_/, "") }
        m.sort!
        res["allow"] = m.join(",")
      end

      private

      def redirect_to_directory_uri(req, res)
        if req.path[-1] != ?/
          location = req.path + "/"
          if req.query_string && req.query_string.size > 0
            location << "?" << req.query_string
          end
          res.set_redirect(HTTPStatus::MovedPermanently, location)
        end
      end

end

#
# stolen from webrick ... i want to mount under apache instead
#

class DefaultFileHandler < AbstractServlet

      def initialize(server, local_path)
        super
        @local_path = local_path
      end

      def do_GET(req, res)
        st = File::stat(@local_path)
        mtime = st.mtime
        res['etag'] = sprintf("%x-%x-%x", st.ino, st.size, st.mtime.to_i)

        if not_modified?(req, res, mtime, res['etag'])
          res.body = ''
          raise HTTPStatus::NotModified
        elsif req['range'] 
          make_partial_content(req, res, @local_path, st.size)
          raise HTTPStatus::PartialContent
        else
          mtype = HTTPUtils::mime_type(@local_path, @config[:MimeTypes])
          res['content-type'] = mtype
          res['content-length'] = st.size
          res['last-modified'] = mtime.httpdate
          res.body = open(@local_path, "rb")
        end
      end

      def not_modified?(req, res, mtime, etag)
        if ir = req['if-range']
          begin
            if Time.httpdate(ir) >= mtime
              return true
            end
          rescue
            if HTTPUtils::split_header_valie(ir).member?(res['etag'])
              return true
            end
          end
        end

        if (ims = req['if-modified-since']) && Time.parse(ims) >= mtime
          return true
        end

        if (inm = req['if-none-match']) &&
           HTTPUtils::split_header_value(inm).member?(res['etag'])
          return true
        end

        return false
      end

      def make_partial_content(req, res, filename, filesize)
        mtype = HTTPUtils::mime_type(filename, @config[:MimeTypes])
        unless ranges = HTTPUtils::parse_range_header(req['range'])
          raise HTTPStatus::BadRequest,
            "Unrecognized range-spec: \"#{req['range']}\""
        end
        open(filename, "rb"){|io|
          if ranges.size > 1
            time = Time.now
            boundary = "#{time.sec}_#{time.usec}_#{Process::pid}"
            body = ''
            ranges.each{|range|
              first, last = prepare_range(range, filesize)
              next if first < 0
              io.pos = first
              content = io.read(last-first+1)
              body << "--" << boundary << CRLF
              body << "Content-Type: #{mtype}" << CRLF
              body << "Content-Range: #{first}-#{last}/#{filesize}" << CRLF
              body << CRLF
              body << content
              body << CRLF
            }
            raise HTTPStatus::RequestRangeNotSatisfiable if body.empty?
            body << "--" << boundary << "--" << CRLF
            res["content-type"] = "multipart/byteranges; boundary=#{boundary}"
            res.body = body
          elsif range = ranges[0]
            first, last = prepare_range(range, filesize)
            raise HTTPStatus::RequestRangeNotSatisfiable if first < 0
            if last == filesize - 1
              content = io.dup
              content.pos = first
            else
              io.pos = first
              content = io.read(last-first+1)
            end
            res['content-type'] = mtype
            res['content-range'] = "#{first}-#{last}/#{filesize}"
            res['content-length'] = last - first + 1
            res.body = content
          else
            raise HTTPStatus::BadRequest
          end
        }
      end

      def prepare_range(range, filesize)
        first = range.first < 0 ? filesize + range.first : range.first
        return -1, -1 if first < 0 || first >= filesize
        last = range.last < 0 ? filesize + range.last : range.last
        last = filesize - 1 if last >= filesize
        return first, last
      end

end

#
# File handler
#

class BaseFileController  < DefaultFileHandler

    def initialize(server,path)
      super(server,path)
    end

=begin
    # attempt at cache control... no idea whats wrong. xxx
    def service(request, response, controllerpath, _found, _ideal, subject, _www)
      # now write simple files - with cookies
      if parts && parts.last && parts.last.index('.')
        # puts request.header['If-Modified-Since'];
        #puts 'base_controller::service() treating as simple file' if @@debug
        # @headers['Cache-control'] = 'max-age=3600,must-revalidate'
        # @headers['Expires'] = 'Fri, 1 Dec 2006 20:00:01 GMT'
        # i have no fucking idea why this is not working. xxx
        # response.header['last-modified'] = 'Thu, 9 Nov 2006 01:00:01 GMT'
        # response.header['expires'] = 'Fri, 14 Dec 2010 20:00:01 GMT'
        # response.header['pragma'] = 'cache'
        # response.header['cache-control'] = 'private, max-age=3600,s-maxage=3600'

    # turn off caching for now
    response.header['expires'] = '-1'
    response.header['pragma'] = 'no-cache'
    response.header['cache-control'] = 'no-cache'

        do_file(request,response)
        return
      end
    end
=end

    def not_modified?(req, res, mtime, etag)
      if ir = req['if-range']
        begin
          if Time.httpdate(ir) >= mtime
            return true
          end
        rescue
          if WEBrick::HTTPUtils::split_header_valie(ir).member?(res['etag'])
            return true
          end
        end
      end
      if (ims = req['if-modified-since']) && Time.parse(ims) >= mtime
        return true
      end
      if (inm = req['if-none-match']) &&
        WEBrick::HTTPUtils::split_header_value(inm).member?(res['etag'])
        return true
      end

      return false
    end

    #
    # my own get routine...  kind of ties me to webrick for now xxx
    #
    def do_GET(req,res,filename)
      st = File::stat(filename)
      mtime = st.mtime
      res['etag'] = sprintf("%x-%x-%x", st.ino, st.size, st.mtime.to_i)
      if not_modified?(req, res, mtime, res['etag'])
        res.body = ''
        raise WEBrick::HTTPStatus::NotModified
      else
        res['content-type'] = WEBrick::HTTPUtils::mime_type(filename, @config[:MimeTypes])
        res['content-length'] = st.size
        res['last-modified'] = mtime.httpdate
        res.body = open(filename, "rb")
      end
    end

    #
    # return static files ( only used for debug - normally apache proxy does this )
    # 
    # strip leading .. / to help make sure we stay in namespace... xxx not good enough
    # 
    # currently static files must be at predictable locations for apache httpd proxying
    # 
    # xxx ideally i'd like to be able to support the same fallback system as everything else
    #     but i see no way of doing this; perhaps apache could just cache results
    #
    def do_file(req,res)
      path = req.path.gsub(/^(\/|\.)+/,"")
      return do_GET(req,res,path) if File.exists? path
      raise WEBrick::HTTPStatus::NotFound, "`#{path}' not found."
    end

end


require 'find'
require 'fileutils'

#
# catch things that look like files
#
class BaseFileRoute
  def self.resolve(request,response,server)
    if request.path != nil && request.path.index('.')
      ( BaseFileController.new(server,'./') ).do_file(request,response)
      return true
    end
    return false
  end
end
